package org.ecbox.sdk.internal.stream.connect;

import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.log4j.Logger;
import org.ecbox.sdk.internal.stream.Configuration;
import org.ecbox.sdk.internal.stream.EopCometSysErrorException;
import org.ecbox.sdk.internal.stream.StreamConstants;

/**
 * http客户端
 * 
 * File: HttpClient.java<br/>
 * Description: <br/>
 * 
 * Copyright: Copyright (c) 2012 ecbox.com<br/>
 * Company: ECBOX,Inc.<br/>
 * 
 * @author chenxiaochun
 * @date 2013-1-4
 * @version 1.0
 */
public class HttpClient {

	private static final Logger log = Logger.getLogger(HttpClient.class);

	/*
	 * 在jdk14以及之前的版本中设置connectionTimeout和readTimeout的方法与1.5之后的不一样
	 */
	private static boolean isJDK14orEarlier = false;

	static {
		try {
			String versionStr = System.getProperty("java.specification.version");
			if (versionStr != null) {
				isJDK14orEarlier = 1.5d > Double.parseDouble(versionStr);
			}
		} catch (Exception ignore) {
			isJDK14orEarlier = false;
		}
	}
	private Map<String, String> params;
	private Configuration conf;

	public HttpClient(Configuration conf, Map<String, String> params) {
		if (conf == null || params == null) {
			throw new RuntimeException("conf and params is must not null");
		}
		this.conf = conf;
		this.params = params;
	}

	public HttpResponse post() throws EopCometSysErrorException {
		int retriedCount;
		int retry = conf.getHttpConnectRetryCount() + 1;
		HttpResponse resp = null;
		for (retriedCount = 1; retriedCount <= retry; retriedCount++) {
			int responseCode = -1;
			try {
				HttpURLConnection con = null;
				OutputStream out = null;
				try {
					con = getConnection(conf.getConnectUrl(), conf.getHttpConnectionTimeout(),
							conf.getHttpReadTimeout());
					con.setDoInput(true);
					con.setDoOutput(true);

					setHeaders(con, conf.getRequestHeader());
					con.setRequestMethod("POST");
					con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
					String postParam = encodeParameters(params);
					if (log.isDebugEnabled()) {
						log.debug("Post param is:" + postParam);
					}
					byte[] bytes = postParam.getBytes("UTF-8");
					con.setRequestProperty("Content-Length", Integer.toString(bytes.length));

					out = con.getOutputStream();
					out.write(bytes);
					out.flush();
					responseCode = con.getResponseCode();
					if (HttpURLConnection.HTTP_OK == responseCode) {
						if (log.isDebugEnabled()) {
							log.debug("connect successful");
							StringBuilder respHeader = new StringBuilder();
							Map<String, List<String>> responseHeaders = con.getHeaderFields();
							for (String key : responseHeaders.keySet()) {
								List<String> values = responseHeaders.get(key);
								for (String value : values) {
									if (key != null) {
										respHeader.append(key).append("=").append(value);
									} else {
										respHeader.append(value);
									}
									respHeader.append(";");
								}
							}
							log.debug("Response: " + respHeader.toString());
						}
						resp = new HttpResponse(con);
						return resp;
					} else if (HttpURLConnection.HTTP_BAD_REQUEST == responseCode) {// 参数校验出错
						String errmsg = con.getHeaderField(StreamConstants.ERR_MSG_HEADER);
						if (log.isDebugEnabled()) {
							log.debug("Request param is invalid,errmsg is:" + errmsg);
						}
						// FIXME 在下一个版本中修复，服务端的问题，
						if (errmsg != null && (errmsg.indexOf("max connect") > -1 || errmsg.indexOf("cache") > -1)) {
							if (retriedCount == conf.getHttpConnectRetryCount()) {
								if (log.isDebugEnabled()) {
									log.debug("May be server occure some error,please contact top tech support");
								}
								throw new EopCometSysErrorException("Server response err msg:" + errmsg);
							}
							try {
								Thread.sleep(retriedCount * conf.getHttpConnectRetryInterval() * 1000);
							} catch (InterruptedException e) {
								// ignore;
							}
							continue;
						} else {
							throw new EopCometSysErrorException("Server response err msg:" + errmsg);
						}
					} else if (HttpURLConnection.HTTP_FORBIDDEN == responseCode) {// 服务端在发布，需要休眠一段时间
						String errmsg = con.getHeaderField(StreamConstants.ERR_MSG_HEADER);
						if (log.isDebugEnabled()) {
							log.debug("Server is deploying,sleep " + retriedCount * conf.getHttpConnectRetryInterval()
									+ " seconds");
						}
						if (retriedCount == conf.getHttpConnectRetryCount()) {
							if (log.isDebugEnabled()) {
								log.debug("May be server occure some error,please contact top tech support");
							}
							throw new EopCometSysErrorException("Server response err msg:" + errmsg);
						}
						try {
							Thread.sleep(retriedCount * conf.getHttpConnectRetryInterval() * 1000);
						} catch (InterruptedException e) {
							// ignore;
						}
						continue;
					} else {// 其他code
						String errmsg = con.getHeaderField(StreamConstants.ERR_MSG_HEADER);
						if (log.isDebugEnabled()) {
							log.debug("Request param is invalid,errmsg is:" + errmsg);
						}
						// FIXME 在下一个版本中修复，服务端的问题，
						if (errmsg != null && (errmsg.indexOf("max connect") > -1 || errmsg.indexOf("cache") > -1)) {
							// 如果发现是超过了最大连接数，那么就慢慢重连吧
							if (retriedCount == conf.getHttpConnectRetryCount()) {
								if (log.isDebugEnabled()) {
									log.debug("May be server occure some error,please contact top tech support");
								}
								throw new EopCometSysErrorException("Server response err msg:" + errmsg);
							}

							try {
								Thread.sleep(retriedCount * conf.getHttpConnectRetryInterval() * 1000);
							} catch (InterruptedException e) {
								// ignore;
							}
							continue;

						} else {
							throw new EopCometSysErrorException("Server response err msg:" + errmsg);
						}
					}
				} finally {
					try {
						if (out != null) {
							out.close();
						}
					} catch (Exception ignore) {
					}
				}
			} catch (IOException ioe) {
				// connection timeout or read timeout
				if (retriedCount == conf.getHttpConnectRetryCount()) {
					throw new EopCometSysErrorException(ioe.getMessage(), ioe);
				}
			}
			try {
				if (log.isDebugEnabled()) {
					log.debug("Sleeping " + conf.getHttpConnectRetryInterval() + " seconds until the next retry.");
				}
				Thread.sleep(retriedCount * conf.getHttpConnectRetryInterval() * 1000);
			} catch (InterruptedException ignore) {
				// nothing to do
			}
		}
		return resp;
	}

	public static String encode(String str) {
		try {
			return URLEncoder.encode(str, "UTF-8");
		} catch (java.io.UnsupportedEncodingException neverHappen) {
			throw new AssertionError("will never happen");
		}
	}

	private String encodeParameters(Map<String, String> params) {
		if (null == params || (params != null && params.size() == 0)) {
			return "";
		}
		StringBuilder buf = new StringBuilder();
		Iterator<Entry<String, String>> it = params.entrySet().iterator();
		int j = 0;
		while (it.hasNext()) {
			Entry<String, String> e = it.next();
			if (j != 0) {
				buf.append("&");
			}
			buf.append(encode(e.getKey())).append("=").append(encode(e.getValue()));
			j++;
		}
		return buf.toString();
	}

	/**
	 * sets HTTP headers
	 * 
	 * @param connection
	 *            HttpURLConnection
	 * @param reqHeader
	 */
	private void setHeaders(HttpURLConnection connection, Map<String, String> reqHeader) {
		if (reqHeader != null && reqHeader.size() > 0) {
			Iterator<Entry<String, String>> it = reqHeader.entrySet().iterator();
			while (it.hasNext()) {
				Entry<String, String> e = it.next();
				connection.addRequestProperty(e.getKey(), e.getValue());
			}
		}
	}

	private HttpURLConnection getConnection(String url, int connTimeout, int readTimeout) throws IOException {
		HttpURLConnection con = null;
		con = (HttpURLConnection) new URL(url).openConnection();
		if (connTimeout > 0) {
			if (!isJDK14orEarlier) {
				con.setConnectTimeout(connTimeout * 1000);
			} else {
				System.setProperty("sun.net.client.defaultConnectTimeout", String.valueOf(connTimeout * 1000));
			}
		}
		if (readTimeout > 0) {
			if (!isJDK14orEarlier) {
				con.setReadTimeout(readTimeout * 1000);
			} else {
				System.setProperty("sun.net.client.defaultReadTimeout", String.valueOf(readTimeout * 1000));
			}
		}
		con.setInstanceFollowRedirects(false);
		return con;
	}
}
